﻿using PMS.Data.Entities.Settings;
using PMS.Services.Utils.Security;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Text;

namespace PMS.Services.Utils.Captcha
{
    public partial class CaptchaService : ICaptchaService
    {
        #region Fields
        private readonly CaptchaSettings _captchaSettings;
        private readonly IEncryptionService _encryptionService;
        #endregion

        #region Ctor
        public CaptchaService(CaptchaSettings captchaSettings,
            IEncryptionService encryptionService)
        {
            _captchaSettings = captchaSettings;
            _encryptionService = encryptionService;
        }
        #endregion

        #region Utils
        /// <summary>
        /// 绘制干扰线
        /// </summary>
        /// <param name="random"></param>
        /// <param name="bitmap"></param>
        /// <param name="graphics"></param>
        /// <param name="lineCount"></param>
        /// <param name="pointCount"></param>
        protected void DrawDisturb(Random random, Bitmap bitmap, Graphics graphics, int lineCount, int pointCount)
        {

            var colors = new List<Color>
            {
                Color.AliceBlue,
                Color.Azure,
                Color.CadetBlue,
                Color.Beige,
                Color.Chartreuse
            };

            //干扰线
            for (var i = 0; i < lineCount; i++)
            {
                var x1 = random.Next(bitmap.Width);
                var x2 = random.Next(bitmap.Width);
                var y1 = random.Next(bitmap.Height);
                var y2 = random.Next(bitmap.Height);

                //Pen 类 定义用于绘制直线和曲线的对象。
                var pen = new Pen(colors[random.Next(0, colors.Count - 1)]);
                graphics.DrawLine(pen, x1, y1, x2, y2);
            }

            //干扰点
            for (var i = 0; i < pointCount; i++)
            {
                var x = random.Next(bitmap.Width);
                var y = random.Next(bitmap.Height);
                bitmap.SetPixel(x, y, Color.FromArgb(random.Next()));
            }
        }
        protected string GenerateCaptchaCodeString(int captchaLen)
        {
            string captchaLetters = _captchaSettings.DisplayLetters;
            if(string.IsNullOrEmpty(captchaLetters))
            {
                var _characters = new List<char>();
                //去掉0、o、O
                for (var c = '0'; c <= '9'; c++)
                {
                    if (c == '0')
                    {
                        continue;
                    }
                    _characters.Add(c);
                }
                for (var c = 'a'; c < 'z'; c++)
                {
                    if (c == 'o')
                    {
                        continue;
                    }
                    _characters.Add(c);
                }
                for (var c = 'A'; c < 'Z'; c++)
                {
                    if (c == 'O')
                    {
                        continue;
                    }
                    _characters.Add(c);
                }
                captchaLetters = string.Join(string.Empty, _characters);
                _characters.Clear();
                _characters = null;
            }
            
            Random rand = new Random();
            int maxRand = captchaLetters.Length - 1;

            StringBuilder sb = new StringBuilder();

            for (int i = 0; i < captchaLen; i++)
            {
                int index = rand.Next(maxRand);
                sb.Append(captchaLetters[index]);
            }

            return sb.ToString();
        }
        #endregion

        #region Methods
        public virtual CaptchaInfo CreateCaptcha(int imgWidth = 85, int imgHeight = 40)
        {
            if (!IsCaptchaEnabled) return null;

            var model = new CaptchaInfo
            {
                ContentType = CaptchaImageFormat.Jpeg,
                CaptchBase64Data = string.Empty
            };

            int codeLen = _captchaSettings.DisplayLetterLength;
            string captchaStr = GenerateCaptchaCodeString(codeLen <= 0 ? 4 : codeLen);
            char[] charArr = captchaStr.ToCharArray();
            //Des加密
            captchaStr = $"{captchaStr}|{DateTime.Now}";
            model.Answer = _encryptionService.EncryptText(captchaStr);

            //var fontNames = FontFamily.Families.Select(_ => _.Name).ToList();
            var fontNames = new List<string>
            {
                "Helvetica","Arial","Lucida Family","Verdana","Tahoma","Trebuchet MS","Georgia","Times"
            };

            //Bitmap 类 封装 GDI+ 包含图形图像和其属性的像素数据的位图。 一个 Bitmap 是用来处理图像像素数据所定义的对象。
            //Bitmap 类 继承自 抽象基类 Image 类
            using (var bitmap = new Bitmap(imgWidth, imgHeight))
            {
                //Graphics 类 封装一个 GDI+ 绘图图面。
                using (var graphics = Graphics.FromImage(bitmap))
                {
                    Random random = new Random();
                    //填充背景色 白色
                    graphics.Clear(Color.White);

                    //绘制干扰线和干扰点
                    DrawDisturb(random, bitmap, graphics, imgWidth / 2, imgHeight);

                    //添加灰色边框
                    var pen = new Pen(Color.Silver);
                    graphics.DrawRectangle(pen, 0, 0, imgWidth - 1, imgHeight - 1);

                    var x = 1;
                    const int y = 5;

                    var rectangle = new Rectangle(0, 0, bitmap.Width, bitmap.Height);

                    var color = Color.FromArgb(random.Next(100, 122), random.Next(100, 122), random.Next(100, 122));

                    foreach (var c in charArr)
                    {
                        //随机选择字符 字体样式和大小
                        var fontName = fontNames[random.Next(0, fontNames.Count - 1)];
                        var font = new Font(fontName, random.Next(18, 25), FontStyle.Bold);
                        //淡化字符颜色 
                        using (var brush = new LinearGradientBrush(rectangle, color, color, 90f, true))
                        {
                            brush.SetSigmaBellShape(0.5f);
                            graphics.DrawString(c.ToString(), font, brush, x + random.Next(-2, 2), y + random.Next(-5, 5));
                            x = x + imgWidth / charArr.Length;
                        }
                    }

                    using (var memoryStream = new MemoryStream())
                    {
                        switch(_captchaSettings.ImageFormat.ToLower())
                        {
                            case "bmp":
                                bitmap.Save(memoryStream, ImageFormat.Bmp);
                                model.Image = memoryStream.ToArray();
                                model.ContentType = CaptchaImageFormat.Bmp;
                                model.CaptchBase64Data = "data:image/bmp;base64," + Convert.ToBase64String(model.Image);
                                break;
                            case "png":
                                bitmap.Save(memoryStream, ImageFormat.Png);
                                model.Image = memoryStream.ToArray();
                                model.ContentType = CaptchaImageFormat.Png;
                                model.CaptchBase64Data = "data:image/png;base64," + Convert.ToBase64String(model.Image);
                                break;
                            case "jpeg":
                            case "jpg":
                            default:
                                bitmap.Save(memoryStream, ImageFormat.Jpeg);
                                model.Image = memoryStream.ToArray();
                                model.ContentType = CaptchaImageFormat.Jpeg;
                                model.CaptchBase64Data = "data:image/jpg;base64," + Convert.ToBase64String(model.Image);
                                break;
                        }
                        

                        return model;
                    }
                }
            }
        }

        /// <summary>
        /// 比对验证码
        /// </summary>
        /// <param name="model"></param>
        /// <param name="timeOut">单位秒，超时时间默认600秒（5分钟）</param>
        /// <returns></returns>
        public virtual CaptchaVerifyResult CaptchaVerify(CaptchaRequest model)
        {
            var result = new CaptchaVerifyResult
            {
                VerifyCode = CaptchaVerifyCode.NotMatch,
                Message = ""
            };
            try
            {
                //判空
                if (string.IsNullOrEmpty(model.InputAnswer) || string.IsNullOrEmpty(model.Captcha))
                {
                    result.VerifyCode = CaptchaVerifyCode.NullCaptcha;
                    result.Message = "验证码校验错误：输入验证码不能为空";
                    return result;
                }

                //Des解密
                var captcha = _encryptionService.DecryptText(model.Captcha);
                var temp = captcha.Split('|');
                if (!DateTime.TryParse(temp[1], out var dateTime))
                {
                    result.VerifyCode = CaptchaVerifyCode.ErrorData;
                    result.Message = "验证码校验错误：非法校验数据";
                    return result;
                }

                var sec = (DateTime.Now - dateTime).TotalSeconds;
                var timeOut = _captchaSettings.ExpireTimeSeconds;
                if (timeOut <= 0) timeOut = 300; //默认5分钟

                if (sec > timeOut)
                {
                    result.VerifyCode = CaptchaVerifyCode.Expired;
                    result.Message = "验证码校验错误：验证码已过期";
                    return result;
                }

                var answer = temp[0];
                if (string.Equals(answer, model.InputAnswer, StringComparison.CurrentCultureIgnoreCase))
                {
                    result.VerifyCode = CaptchaVerifyCode.Success;
                    result.Message = "校验成功";
                    return result;
                }

                result.VerifyCode = CaptchaVerifyCode.NotMatch;
                result.Message = "验证码校验错误：验证码未匹配";
                return result;
            }
            catch (Exception ex)
            {
                result.VerifyCode = CaptchaVerifyCode.Exception;
                result.Message = ex.Message;
                return result;
            }
        }
        #endregion

        #region Properties
        public bool IsCaptchaEnabled
        {
            get => _captchaSettings.Enabled;
        }
        #endregion
    }
}
